package com.git.hui.yinshua.biz.pay.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.git.hui.yinshua.api.model.ExceptionUtil;
import com.git.hui.yinshua.api.model.ReqInfoContext;
import com.git.hui.yinshua.api.model.Status;
import com.git.hui.yinshua.api.model.constants.GlobalConstants;
import com.git.hui.yinshua.api.model.order.OrderStatusEnum;
import com.git.hui.yinshua.api.model.order.OrderTypeEnum;
import com.git.hui.yinshua.api.model.order.PayStatusEnum;
import com.git.hui.yinshua.api.model.order.TradeStatusEnum;
import com.git.hui.yinshua.biz.order.dao.entity.OrderEntity;
import com.git.hui.yinshua.biz.order.service.OrderService;
import com.git.hui.yinshua.biz.pay.dao.entity.OrderPaymentEntity;
import com.git.hui.yinshua.biz.pay.dao.repository.OrderPaymentRepository;
import com.git.hui.yinshua.biz.pay.model.PreOrderDynamicParam;
import com.git.hui.yinshua.biz.pay.service.convert.PaymentConvert;
import com.git.hui.yinshua.biz.pay.service.wx.IWxPayService;
import com.git.hui.yinshua.biz.rest.models.order.rsp.OrderDto;
import com.git.hui.yinshua.biz.rest.models.order.rsp.PaymentDto;
import com.git.hui.yinshua.biz.rest.models.order.rsp.PrePayInfoDo;
import com.git.hui.yinshua.common.DateUtil;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 支付服务
 *
 * @author YiHui
 * @date 2024/9/9
 */
@Slf4j
@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;
    @Autowired
    private OrderPaymentRepository orderPaymentRepository;
    @Autowired
    private IWxPayService wxPayService;

    public Map<Long, PaymentDto> listSimplePaymentInfo(Collection<Long> orderIds) {
        List<OrderPaymentEntity> payments = orderPaymentRepository.batchQueryByOrderIs(orderIds);
        if (CollectionUtils.isEmpty(payments)) {
            return Collections.emptyMap();
        }
        return payments.stream().map(PaymentConvert::toPayment).collect(Collectors.toMap(PaymentDto::getOrderId, s -> s));
    }


    /**
     * 生成支付单
     *
     * @param orderId
     */
    @Transactional
    public PaymentDto genPayment(Long orderId) {
        return genPayment(orderService.detail(orderId));
    }

    @Transactional
    public PaymentDto genPayment(OrderDto order) {
        // 1. 获取支付单
        OrderPaymentEntity payment = orderPaymentRepository.getOrSavePayment(order);
        if (payment == null || payment.getId() == null) {
            throw ExceptionUtil.of(Status.StatusEnum.BIZ_EXCEPTION, "下单异常!");
        }

        return this.getPaymentInfo(order, payment);
    }

    /**
     * 获取订单上的支付信息
     *
     * @param order
     * @param payment
     * @return
     */
    public PaymentDto getPaymentInfo(OrderDto order, OrderPaymentEntity payment) {
        if (payment == null) {
            payment = orderPaymentRepository.queryByOrderId(order.getOrderId());
        }

        if (payment == null) {
            return null;
        }

        if (Objects.equals(payment.getPayStatus(), PayStatusEnum.SUCCEED.getStatus())
                || Objects.equals(payment.getPayStatus(), PayStatusEnum.PAYING.getStatus())
                || Objects.equals(payment.getPayStatus(), PayStatusEnum.REFUNDED.getStatus())
                || Objects.equals(payment.getPayStatus(), PayStatusEnum.REFUNDING.getStatus())) {
            // 支付中/已支付，退款中/已退款 直接返回
            return PaymentConvert.toPayment(payment);
        }
        // 订单状态已经往后流转了，直接返回
        if (order.getOrderStatus() > OrderStatusEnum.TO_PAY.getStatus()
                && !payment.getPayStatus().equals(PayStatusEnum.TO_PAY.getStatus())) {
            return PaymentConvert.toPayment(payment);
        }

        // 对于待支付的支付单，
        // 1. 查询历史的prePayId
        boolean legalPrePayId = (System.currentTimeMillis() / 1000) < payment.getPreExpireTime() - GlobalConstants.PayApi.PRE_PAY_CHECK_EXPIRE_TIME_INTERVAL;
        if (StringUtils.isNotBlank(payment.getPrePayId())) {
            if (legalPrePayId) {
                // 2.1 prePayId有效，直接返回
                PaymentDto dto = PaymentConvert.toPayment(payment);
                dto.setPrePayInfo(wxPayService.genToPayPrePayInfo(payment.getPrePayId(), payment.getUuid()));
                return dto;
            } else {
                // 2.2 过期了，关闭旧的prePayId
                payment.setPrePayId("");
            }
        }

        // 3. 计算用户唤起支付的prePayId
        try {
            PreOrderDynamicParam preOrder = new PreOrderDynamicParam();
            preOrder.setOpenId(payment.getOpenId());
            preOrder.setOutTradeNo(payment.getUuid());
            String orderDate = DateUtil.time2date(order.getCreateAt());
            if (order.getType().equals(OrderTypeEnum.PT.getType())) {
                // 拼团订单
                preOrder.setDescription(Optional.ofNullable(order.getUser().getNickName()).orElse(order.getUser().getUserName()) + "的拼团单-" + orderDate);
            } else {
                preOrder.setDescription(Optional.ofNullable(order.getUser().getNickName()).orElse(order.getUser().getUserName()) + "的订单-" + orderDate);
            }
            preOrder.setTotal(payment.getPayAmount());
            // 生成订单
            PrePayInfoDo map = wxPayService.jsApiOrder(preOrder);
            // 官方文档，两个小时的有效期
            Long expire = (System.currentTimeMillis() + GlobalConstants.PayApi.PRE_PAY_EXPIRE_TIME_PERIOD);
            payment.setPrePayId(map.getPrePayId());
            payment.setPreExpireTime(expire);
            orderPaymentRepository.updatePrePayInfo(payment);
            PaymentDto res = PaymentConvert.toPayment(payment);
            res.setPrePayInfo(map);
            return res;
        } catch (Exception e) {
            log.error("唤起用户支付失败: {}", payment, e);
            return PaymentConvert.toPayment(payment);
        }
    }


    /**
     * 微信支付回调
     *
     * @param transaction
     */
    @Transactional
    public boolean wxPayCallback(Transaction transaction) {
        PayStatusEnum payStatusEnum;
        TradeStatusEnum tradeStatus;
        if (transaction.getTradeState() == Transaction.TradeStateEnum.NOTPAY) {
            // 待支付
            payStatusEnum = PayStatusEnum.TO_PAY;
            tradeStatus = TradeStatusEnum.NOT_PAY;
        } else if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
            // 支付成功
            payStatusEnum = PayStatusEnum.SUCCEED;
            tradeStatus = TradeStatusEnum.PAY_SUCCEED;
        } else if (transaction.getTradeState() == Transaction.TradeStateEnum.USERPAYING) {
            // 支付中
            payStatusEnum = PayStatusEnum.PAYING;
            tradeStatus = TradeStatusEnum.PAYING;
        } else {
            // 其他的都归入支付失败
            payStatusEnum = PayStatusEnum.FAILED;
            tradeStatus = TradeStatusEnum.FAIL;
        }

        OrderPaymentEntity payment = orderPaymentRepository.queryForLockByOutId(transaction.getOutTradeNo());
        if (payment == null) {
            // 没有找到支付单，直接返回
            log.error("没有找到支付单: {}", transaction);
            return false;
        }

        if (Objects.equals(payment.getPayStatus(), PayStatusEnum.SUCCEED.getStatus())) {
            if (Objects.equals(payStatusEnum.getStatus(), PayStatusEnum.SUCCEED.getStatus())) {
                log.info("支付单状态已经是已支付了，无需继续更新: {}-{}", JSONObject.toJSONString(transaction), JSONObject.toJSONString(payment));
            } else if (Objects.equals(payStatusEnum.getStatus(), PayStatusEnum.FAILED.getStatus())) {
                log.error("先成功，后告诉支付失败，请人工确认订单的支付状态！ 订单：{} 支付单: {}", payment.getOrderId(), payment.getId());
            }
            return true;
        }

        // 首先对订单加锁，避免重复操作导致各种问题
        OrderEntity order = orderService.lockOrder(payment.getOrderId());
        if (Objects.equals(order.getPayStatus(), PayStatusEnum.SUCCEED.getStatus())) {
            // 支付单已是成功状态，不做后续的操作
            if (Objects.equals(payStatusEnum.getStatus(), PayStatusEnum.SUCCEED.getStatus())) {
                log.info("支付单状态已经是已支付了，无需继续更新: {}-{}", JSONObject.toJSONString(transaction), JSONObject.toJSONString(payment));
            } else if (Objects.equals(payStatusEnum.getStatus(), PayStatusEnum.FAILED.getStatus())) {
                log.error("先成功，后告诉支付失败，请人工确认订单的支付状态！ 订单：{} 支付单: {}", payment.getOrderId(), payment.getId());
            }
            return true;
        }


        // 更新支付单状态
        OrderPaymentEntity updatePayment = new OrderPaymentEntity();
        updatePayment.setId(payment.getId());
        updatePayment.setPayStatus(payStatusEnum.getStatus());
        updatePayment.setThirdTransCode(transaction.getTransactionId());
        updatePayment.setRemark(JSON.toJSONString(transaction));
        updatePayment.setTradeStatus(tradeStatus.getStatus());
        // 更新支付单状态
        orderPaymentRepository.updatePayInfo(updatePayment);

        // 订单回调处理
        orderService.paymentCallback(payment.getOrderId(), payStatusEnum, transaction.getSuccessTime() != null ? DateUtil.wxDayToTimestamp(transaction.getSuccessTime()) : null);
        return true;
    }

    /**
     * 接收前端回传的支付结果，直接更新订单的支付单状态为支付中；等待后台接收微信通知之后，才表示支付成功，订单流转到下一个状态
     *
     * @param orderId
     * @return
     */
    @Transactional
    public boolean paying(Long orderId) {
        OrderEntity order = orderService.lockOrder(orderId);
        if (!Objects.equals(order.getUid(), ReqInfoContext.getReqInfo().getUserId())) {
            throw ExceptionUtil.of(Status.StatusEnum.FORBID_ERROR);
        }

        if (Objects.equals(order.getPayStatus(), PayStatusEnum.TO_PAY.getStatus())) {
            // 只有待支付时，才需要将支付状态更新为支付中
            orderPaymentRepository.updatePaying(orderId);
            // 更新订单的支付状态为支付中
            return orderService.paymentCallback(order.getId(), PayStatusEnum.PAYING, null);
        }

        return true;
    }
}
